/*              Copyright(c) 2008 Dave Sexton              *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.IO;
using System.Xml;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace DaveSexton.DocToMaml
{
	internal abstract class ProjectManagerBase<TProject> : IProjectManager, IDisposable
		where TProject : class, IProject
	{
		#region Public Properties
		public virtual bool IsDirty
		{
			get
			{
				if (!loaded)
					throw new InvalidOperationException(Resources.Errors.ProjectNotLoaded);

				return dirty;
			}
			set
			{
				if (!loaded)
					throw new InvalidOperationException(Resources.Errors.ProjectNotLoaded);

				if (dirty != value)
				{
					dirty = value;

					OnIsDirtyChanged(EventArgs.Empty);
				}
			}
		}

		public bool IsNew
		{
			get
			{
				return isNew;
			}
		}

		public string FilePath
		{
			get
			{
				if (!loaded)
					throw new InvalidOperationException(Resources.Errors.ProjectNotLoaded);

				return project.FilePath;
			}
		}

		public virtual string Name
		{
			get
			{
				if (!loaded)
					throw new InvalidOperationException(Resources.Errors.ProjectNotLoaded);

				return System.IO.Path.GetFileNameWithoutExtension(project.FilePath);
			}
		}

		public bool Loaded
		{
			get
			{
				return loaded;
			}
		}

		public TProject Project
		{
			get
			{
				if (!loaded)
					throw new InvalidOperationException(Resources.Errors.ProjectNotLoaded);

				return project;
			}
		}

		public abstract string DefaultFileExtension { get; }
		#endregion

		#region Private / Protected
		protected EventHandlerList Events
		{
			get
			{
				if (events == null)
					events = new EventHandlerList();

				return events;
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
		protected ProgramForm Owner
		{
			get
			{
				return owner;
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
		protected IUIService UI
		{
			get
			{
				return uiService;
			}
		}

		private TProject project;
		private bool dirty, loaded, isNew;
		private EventHandlerList events;
		private readonly ProgramForm owner;
		private readonly IUIService uiService;
		private readonly IServiceProvider services;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ProjectManagerBase" /> class for derived classes.
		/// </summary>
		public ProjectManagerBase(ProgramForm owner, IServiceProvider services)
		{
			if (owner == null)
				throw new ArgumentNullException("owner");

			if (services == null)
				throw new ArgumentNullException("services");

			this.owner = owner;
			this.services = services;

			uiService = (IUIService) services.GetService(typeof(IUIService));
		}
		#endregion

		#region Methods
		protected abstract TProject CreateProject(string file);
		
		public bool Close(bool forceSave)
		{
			if (!loaded)
				throw new InvalidOperationException(Resources.Errors.ProjectNotLoaded);

			if (dirty)
			{
				bool? result = Save(!forceSave);

				if (!result.HasValue)
					// operation canceled by user
					return false;
			}

			return Close();
		}

		private bool Close()
		{
			if (loaded)
			{
				CancelEventArgs e = new CancelEventArgs();

				OnClosing(e);

				if (e.Cancel)
					return false;

				Reset();

				OnClosed(EventArgs.Empty);
			}

			return true;
		}

		private void Reset()
		{
			project = null;
			dirty = false;
			loaded = false;
			isNew = false;
		}

		public bool OpenNew()
		{
			if (!TrySave() || !Close())
				return false;

			project = CreateProject(Resources.Text.NewProjectFileName + DefaultFileExtension);

			isNew = true;
			loaded = true;
			dirty = false;

			OnInitializeNewProject(EventArgs.Empty);
			OnOpened(new ProjectOpenedEventArgs(true));

			return true;
		}

		public bool Open()
		{
			if (!TrySave())
				return false;

			return PromptOpen(ProgramSettings.DefaultProjectDirectory);
		}

		public bool Open(string projectFileOrDirectory)
		{
			if (string.IsNullOrEmpty(projectFileOrDirectory))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "projectFileOrDirectory");

			if (string.IsNullOrEmpty(Path.GetExtension(projectFileOrDirectory)))
				return PromptOpen(projectFileOrDirectory);
			else if (!TrySave() || !Close())
				return false;

			project = CreateProject(projectFileOrDirectory);

			return OpenInternal();
		}

		private bool PromptOpen(string directory)
		{
			using (OpenFileDialog dialog = new OpenFileDialog())
			{
				dialog.CheckFileExists = true;
				dialog.CheckPathExists = true;
				dialog.Filter = Resources.Text.ProjectFilesDialogFilter;
				dialog.Title = Resources.Text.OpenProjectDialogTitle;

				if (!string.IsNullOrEmpty(directory))
					dialog.InitialDirectory = (Path.GetExtension(directory).Length > 0) ? Path.GetDirectoryName(directory) : directory;

				if (dialog.ShowDialog(owner) == DialogResult.OK)
				{
					if (!Close())
						return false;

					project = CreateProject(dialog.FileName);

					return OpenInternal();
				}
			}

			return false;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private bool OpenInternal()
		{
			try
			{
				// NOTE: Set loaded to true before reading the project file so that derived classes can read the FilePath property during parsing.
				loaded = true;

				project.Load();
			}
			catch (DirectoryNotFoundException)
			{
				uiService.ShowError(string.Format(System.Globalization.CultureInfo.CurrentCulture,
					Resources.Errors.ProjectFileDoesNotExist, Environment.NewLine, project.FilePath));

				Reset();
			}
			catch (FileNotFoundException)
			{
				uiService.ShowError(string.Format(System.Globalization.CultureInfo.CurrentCulture,
					Resources.Errors.ProjectFileDoesNotExist, Environment.NewLine, project.FilePath));

				Reset();
			}
			catch (Exception ex)
			{
				uiService.ShowError(ex, string.Format(System.Globalization.CultureInfo.CurrentCulture,
					Resources.Errors.InvalidProjectFile, Environment.NewLine));

				Reset();
			}

			if (loaded)
				OnOpened(new ProjectOpenedEventArgs(false));

			return loaded;
		}

		private bool TrySave()
		{
			if (dirty)
			{
				bool? result = Save(true);

				if (!result.HasValue)
					// operation canceled by user
					return false;
			}

			return true;
		}

		private bool? Save(bool askFirst)
		{
			if (askFirst)
			{
				switch (uiService.ShowMessage(Resources.Text.AskSaveProject, Resources.Text.AskSaveProjectCaption, MessageBoxButtons.YesNoCancel))
				{
					case DialogResult.No:
						return false;
					case DialogResult.Cancel:
						return null;
				}
			}

			if (isNew)
				SaveAs();
			else
				SaveInternal(null);

			return true;
		}

		public void Save()
		{
			if (!loaded)
				throw new InvalidOperationException(Resources.Errors.ProjectNotLoaded);

			if (dirty)
				Save(false);
		}

		public bool SaveAs()
		{
			if (!loaded)
				throw new InvalidOperationException(Resources.Errors.ProjectNotLoaded);

			using (SaveFileDialog dialog = new SaveFileDialog())
			{
				dialog.AddExtension = true;
				dialog.CheckFileExists = false;
				dialog.CheckPathExists = true;
				dialog.CreatePrompt = false;
				dialog.DefaultExt = DefaultFileExtension;
				dialog.FileName = Path.GetFileNameWithoutExtension(project.FilePath);
				dialog.Filter = Resources.Text.ProjectFilesDialogFilter;
				dialog.InitialDirectory = (string.IsNullOrEmpty(Path.GetDirectoryName(project.FilePath))) ? ProgramSettings.DefaultProjectDirectory : project.FilePath;
				dialog.OverwritePrompt = true;
				dialog.ShowHelp = false;
				dialog.Title = Resources.Text.SaveProjectAsDialogTitle;

				if (dialog.ShowDialog(owner) == DialogResult.OK)
				{
					SaveInternal(dialog.FileName);

					return true;
				}
			}

			return false;
		}

		private void SaveInternal(string file)
		{
			if (file == null)
				project.Save();
			else
				project.SaveAs(file);

			isNew = false;
			dirty = false;

			OnSaved(EventArgs.Empty);
		}

		public object GetService(Type serviceType)
		{
			return services.GetService(serviceType);
		}
		#endregion

		#region Events
		private readonly object ClosedEvent = new object();
		private readonly object SavedEvent = new object();
		private readonly object OpenedEvent = new object();
		private readonly object IsDirtyChangedEvent = new object();
		private readonly object ClosingEvent = new object();
		private readonly object InitializeNewProjectEvent = new object();

		/// <summary>
		/// Event raised when a new project is opened.
		/// </summary>
		[Category("Behavior")]
		[Description("Event raised when a new project is opened.")]
		public event EventHandler<EventArgs> InitializeNewProject
		{
			add
			{
				lock (InitializeNewProjectEvent)
				{
					Events.AddHandler(InitializeNewProjectEvent, value);
				}
			}
			remove
			{
				lock (InitializeNewProjectEvent)
				{
					Events.RemoveHandler(InitializeNewProjectEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="InitializeNewProject" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnInitializeNewProject(EventArgs e)
		{
			EventHandler<EventArgs> handler = null;

			lock (InitializeNewProjectEvent)
			{
				handler = (EventHandler<EventArgs>) Events[InitializeNewProjectEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the project is about to close.
		/// </summary>
		[Category("Behavior")]
		[Description("Event raised when the project is about to close.")]
		public event CancelEventHandler Closing
		{
			add
			{
				lock (ClosingEvent)
				{
					Events.AddHandler(ClosingEvent, value);
				}
			}
			remove
			{
				lock (ClosingEvent)
				{
					Events.RemoveHandler(ClosingEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Closing" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnClosing(CancelEventArgs e)
		{
			CancelEventHandler handler = null;

			lock (ClosingEvent)
			{
				handler = (CancelEventHandler) Events[ClosingEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="IsDirty"/> property value changes.
		/// </summary>
		[Category("Property Changed")]
		[Description("Event raised when the IsDirty property value changes.")]
		public event EventHandler IsDirtyChanged
		{
			add
			{
				lock (IsDirtyChangedEvent)
				{
					Events.AddHandler(IsDirtyChangedEvent, value);
				}
			}
			remove
			{
				lock (IsDirtyChangedEvent)
				{
					Events.RemoveHandler(IsDirtyChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="IsDirtyChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnIsDirtyChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (IsDirtyChangedEvent)
			{
				handler = (EventHandler) Events[IsDirtyChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the project is opened.
		/// </summary>
		[Category("Behavior")]
		[Description("Event raised when the project is opened.")]
		public event EventHandler<ProjectOpenedEventArgs> Opened
		{
			add
			{
				lock (OpenedEvent)
				{
					Events.AddHandler(OpenedEvent, value);
				}
			}
			remove
			{
				lock (OpenedEvent)
				{
					Events.RemoveHandler(OpenedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Opened" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnOpened(ProjectOpenedEventArgs e)
		{
			EventHandler<ProjectOpenedEventArgs> handler = null;

			lock (OpenedEvent)
			{
				handler = (EventHandler<ProjectOpenedEventArgs>) Events[OpenedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the project is saved.
		/// </summary>
		[Category("Behavior")]
		[Description("Event raised when the project is saved.")]
		public event EventHandler Saved
		{
			add
			{
				lock (SavedEvent)
				{
					Events.AddHandler(SavedEvent, value);
				}
			}
			remove
			{
				lock (SavedEvent)
				{
					Events.RemoveHandler(SavedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Saved" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnSaved(EventArgs e)
		{
			EventHandler handler = null;

			lock (SavedEvent)
			{
				handler = (EventHandler) Events[SavedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the project is closed.
		/// </summary>
		[Category("Behavior")]
		[Description("Event raised when the project is closed.")]
		public event EventHandler Closed
		{
			add
			{
				lock (ClosedEvent)
				{
					Events.AddHandler(ClosedEvent, value);
				}
			}
			remove
			{
				lock (ClosedEvent)
				{
					Events.RemoveHandler(ClosedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Closed" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnClosed(EventArgs e)
		{
			EventHandler handler = null;

			lock (ClosedEvent)
			{
				handler = (EventHandler) Events[ClosedEvent];
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region IDisposable Members
		/// <summary>
		/// Releases all resources used by an instance of the <see cref="ProjectManagerBase" /> class.
		/// </summary>
		/// <remarks>
		/// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
		/// finalization of the instance.
		/// </remarks>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged resources before an instance of the <see cref="ProjectManagerBase" /> class is reclaimed by garbage collection.
		/// </summary>
		/// <remarks>
		/// This method releases unmanaged resources by calling the virtual <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
		/// </remarks>
		~ProjectManagerBase()
		{
			Dispose(false);
		}

		/// <summary>
		/// Releases the unmanaged resources used by an instance of the <see cref="ProjectManagerBase" /> class and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (events != null)
					events.Dispose();
			}
		}
		#endregion
	}
}
